package com.tobeface.wechat.request;

import com.tobeface.modules.lang.Requires;
import com.tobeface.wechat.core.WeChatException;
import com.tobeface.wechat.core.WeChatSettings;
import com.tobeface.wechat.core.WeChatVersion;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.Map;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.locks.ReentrantLock;

/**
 * @author loudyn
 */
public class DefaultWeChatRequestService implements WeChatRequestService {

    private static final String GET_ACCESS_TOKEN_URL = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=%s&secret=%s";
    private static final String ACCESS_TOKEN_QUERY_PARAM = "access_token";

    private final Logger logger = LoggerFactory.getLogger(getClass());
    private final ReentrantLock accquireAccessTokenLock = new ReentrantLock();
    private final AtomicReference<WeChatToken> accessToken = new AtomicReference<WeChatToken>();

    private final WeChatSettings settings;
    private final WeChatRequestSinker sinker;
    private final WeChatRequestStrategy requestStrategy;

    /**
     * @param settings
     */
    public DefaultWeChatRequestService(WeChatSettings settings) {
        this(settings, new URLConnectionWeChatRequestSinker());
    }


    /**
     * @param settings
     * @param sinker
     */
    public DefaultWeChatRequestService(WeChatSettings settings, WeChatRequestSinker sinker) {
        this(settings, sinker, new OnceWeChatRequestStrategy());
    }

    /**
     * @param settings
     * @param sinker
     * @param requestStrategy
     */
    public DefaultWeChatRequestService(WeChatSettings settings,
                                       WeChatRequestSinker sinker,
                                       WeChatRequestStrategy requestStrategy) {

        Requires.notNull(settings, "Settings must not be null!");
        Requires.notNull(sinker, "Sinker must not be null!");
        Requires.notNull(requestStrategy, "RequestStrategy must not be null!");

        this.settings = settings;
        this.sinker = sinker;
        this.requestStrategy = requestStrategy;
    }

    @Override
    public WeChatResponse sendRequest(WeChatRequest request) {

        if (null == getAccessToken().get()) {
            doAccquireAccessToken(settings);
            return sendRequest(request);
        }

        if (getAccessToken().get().isExpired()) {
            doRefreshAccessToken(settings, getAccessToken().get());
            return sendRequest(request);
        }

        // add accessToken to request.
        request.addQueryParam(ACCESS_TOKEN_QUERY_PARAM, getAccessToken().get().getToken());
        return sinker.sinkRequest(requestStrategy, request);
    }

    /**
     * @param settings
     */
    protected final void doAccquireAccessToken(WeChatSettings settings) {

        try {

            accquireLock();
            // check it again.
            if (null != getAccessToken().get()) {
                return;
            }

            WeChatToken token = doAccquireAccessTokenInternal(settings);
            getAccessToken().set(token);
        } finally {
            releaseLock();
        }
    }

    /**
     * release lock
     */
    protected void releaseLock() {
        accquireAccessTokenLock.unlock();
    }

    /**
     * accquire lock
     */
    protected void accquireLock() {
        accquireAccessTokenLock.lock();
    }

    protected WeChatToken doAccquireAccessTokenInternal(WeChatSettings settings) {
        String url = String.format(GET_ACCESS_TOKEN_URL, settings.getAppKey(), settings.getAppSecret());
        WeChatRequest req = WeChatRequest.of(url, WeChatRequest.Verb.GET);
        WeChatResponse resp = sinker.sinkRequest(requestStrategy, req);
        if (!resp.isOK()) {
            throw resolveWeChatException(settings, resp);
        }

        Map<String, Object> accessTokeInfo = (Map<String, Object>) resp.get();
        return WeChatToken.newBuilder().token((String) accessTokeInfo.get("access_token"))
                                        .expiredAfterSeconds((Long) accessTokeInfo.get("expires_in"))
                                        .build();
    }

    private WeChatException resolveWeChatException(WeChatSettings settings, WeChatResponse resp) {
        // TODO
        return new WeChatException();
    }

    /**
     * @param settings
     * @param expiredToken
     */
    private void doRefreshAccessToken(WeChatSettings settings, WeChatToken expiredToken) {
        doAccquireAccessToken(settings);
    }

    /**
     * @return
     */
    protected final AtomicReference<WeChatToken> getAccessToken() {
        return accessToken;
    }

    /**
     * @return
     */
    protected final Logger getLogger() {
        return logger;
    }

    @Override
    public WeChatVersion getVersion() {
        return WeChatVersion.V1;
    }
}
